/*
Copyright (C) 2012 John Tsiombikas <nuclear@member.fsf.org>,
                   Ioannis Makris <imak@imak.gr>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/* The window-specific glory_ functions just handle the id<->window mapping
 * and the window linked list, and call the internal gloin_ functions to do
 * the rest of the job.
 */
#include <stdlib.h>
#include <string.h>
#include "glory.h"
#include "gloin.h"
#include "wsys.h"
#include "log.h"

#define CHECK_ACTIVE_WIN \
	do { \
		if(!active_win) {\
			gloin_log("%s: no active window!\n", __FUNCTION__); \
			return; \
		} \
	} while(0)


static struct window *find_window(int id);

/* window list */
static struct window *winlist;
static struct window *active_win;


void gloin_set_active(struct window *win)
{
	active_win = win;

	gloin_wsys_set_active(win);
}

struct window *gloin_get_active(void)
{
	return active_win;
}

struct window *gloin_winlist(void)
{
	return winlist;
}


/* ---- implementation of the public interface of glory ---- */

int glory_init(void)
{
	winlist = active_win = 0;

	/* initialize the window system module */
	return gloin_wsys_init();
}

void glory_cleanup(void)
{
	/* destroy all active windows */
	while(winlist) {
		struct window *win = winlist;
		winlist = winlist->next;
		gloin_destroy_window(win);
	}
	active_win = 0;

	/* give a change to the window system module to do cleanup */
	gloin_wsys_cleanup();
}

int glory_set_video_mode(int xsz, int ysz, int rate)
{
	return gloin_wsys_set_video_mode(xsz, ysz, rate);
}

int glory_get_video_mode(int *xsz, int *ysz, int *rate)
{
	return gloin_wsys_get_video_mode(xsz, ysz, rate);
}

/* detailed OpenGL context attribute setting */
void glory_attr_color(int rbits, int gbits, int bbits)
{
	gloin_attr.rbits = rbits;
	gloin_attr.gbits = gbits;
	gloin_attr.bbits = bbits;
}

void glory_attr_doublebuf(int enable)
{
	gloin_attr.doublebuf = enable;
}

void glory_attr_depth(int dbits)
{
	gloin_attr.zbits = dbits;
}

void glory_attr_stencil(int sbits)
{
	gloin_attr.sbits = sbits;
}

void glory_attr_stereo(int enable)
{
	gloin_attr.stereo = enable;
}

void glory_attr_multisample(int samples)
{
	gloin_attr.msamples = samples;
}

void glory_attr_api(int glapi)
{
	gloin_attr.api = glapi;
}


int glory_create_window(int xsz, int ysz, unsigned int mode)
{
	struct window *win;
	static int next_id = 1;

	if(!(win = gloin_create_window(xsz, ysz, mode))) {
		return -1;
	}

	win->id = next_id++;

	win->next = winlist;
	winlist = win;

	gloin_set_active(win);
	return win->id;
}

void glory_destroy_window(int id)
{
	struct window *win, dummy;

	dummy.next = winlist;
	win = &dummy;

	while(win->next) {
		if(win->next->id == id) {
			struct window *tmp = win->next;
			if(winlist == tmp) {
				winlist = tmp->next;
			}
			win->next = tmp->next;
			gloin_destroy_window(tmp);
			break;
		}
		win = win->next;
	}
	if (!winlist) {
		glory_quit();
	}
}

void glory_fullscreen(void)
{
	/* TODO */
}

void glory_windowed(void)
{
	/* TODO */
}

/* this function has no gloin counterpart, it just handles the global
 * "current window" state.
 */
void glory_set_active(int id)
{
	if(id > 0) {
		struct window *win = find_window(id);
		if(win) {
			active_win = win;
			gloin_set_active(win);
		}
	} else {
		active_win = 0;
	}
}

int glory_get_active(void)
{
	return active_win ? active_win->id : 0;
}

void glory_resize(int width, int height)
{
	CHECK_ACTIVE_WIN;
	gloin_wsys_resize_window(active_win, width, height);
}

void glory_set_title(const char *str)
{
	CHECK_ACTIVE_WIN;
	gloin_wsys_set_title(active_win, str);
}

void glory_redisplay(void)
{
	CHECK_ACTIVE_WIN;
	gloin_wsys_redisplay(active_win);
}

void glory_swap_buffers(void)
{
	CHECK_ACTIVE_WIN;
	gloin_wsys_swap_buffers(active_win);
}

unsigned int glory_modifiers(void)
{
	return gloin_wsys_modifiers();
}

int glory_mouse_id(void)
{
	if(!active_win) {
		gloin_log("%s: no active window!\n", __FUNCTION__);
		return 0;
	}
	return gloin_mouse_id(active_win);
}

void glory_quit(void)
{
	gloin_wsys_quit();
}

void glory_event_loop(void)
{
	gloin_wsys_event_loop();
}


void glory_push_callbacks(void)
{
	CHECK_ACTIVE_WIN;
	gloin_push_callbacks(active_win);
}

void glory_pop_callbacks(void)
{
	CHECK_ACTIVE_WIN;
	gloin_pop_callbacks(active_win);
}

void glory_clear_callbacks(void)
{
	CHECK_ACTIVE_WIN;
	gloin_clear_callbacks(active_win);
}

void glory_set_callback(int idx, void (*func)(), void *cls)
{
	if(idx < 0 || idx >= GLORY_NUM_CALLBACKS) {
		gloin_log("glory_set_callback: invalid callback number: %d\n", idx);
		return;
	}

	CHECK_ACTIVE_WIN;
	gloin_set_callback(active_win, idx, func, cls);
}

void (*glory_get_callback(int idx))()
{
	if(idx < 0 || idx >= GLORY_NUM_CALLBACKS) {
		gloin_log("glory_get_callback: invalid callback number: %d\n", idx);
		return 0;
	}
	if(!active_win) {
		gloin_log("glory_get_callback: no active window\n");
		return 0;
	}
	return gloin_get_callback(active_win, idx);
}

void *glory_get_callback_data(int idx)
{
	if(idx < 0 || idx >= GLORY_NUM_CALLBACKS) {
		gloin_log("glory_get_callback_data: invalid callback number: %d\n", idx);
		return 0;
	}
	if(!active_win) {
		gloin_log("glory_get_callback: no active window\n");
		return 0;
	}
	return gloin_get_callback_data(active_win, idx);
}


void glory_create_callback(glory_create_callback_t func, void *cls)
{
	glory_set_callback(GLORY_CREATE, func, cls);
}

void glory_destroy_callback(glory_destroy_callback_t func, void *cls)
{
	glory_set_callback(GLORY_DESTROY, func, cls);
}

void glory_display_callback(glory_display_callback_t func, void *cls)
{
	glory_set_callback(GLORY_DISPLAY, func, cls);
}

void glory_reshape_callback(glory_reshape_callback_t func, void *cls)
{
	glory_set_callback(GLORY_RESHAPE, func, cls);
}

void glory_keyboard_callback(glory_keyboard_callback_t func, void *cls)
{
	glory_set_callback(GLORY_KEYBOARD, func, cls);
}

void glory_mouse_callback(glory_mouse_callback_t func, void *cls)
{
	glory_set_callback(GLORY_MOUSE, func, cls);
}

void glory_motion_callback(glory_motion_callback_t func, void *cls)
{
	glory_set_callback(GLORY_MOTION, func, cls);
}

void glory_passive_callback(glory_passive_callback_t func, void *cls)
{
	glory_set_callback(GLORY_PASSIVE, func, cls);
}

void glory_space_callback(glory_space_callback_t func, void *cls)
{
	glory_set_callback(GLORY_SPACE, func, cls);
}

void glory_button_callback(glory_button_callback_t func, void *cls)
{
	glory_set_callback(GLORY_BUTTON, func, cls);
}

void glory_idle_callback(glory_idle_callback_t func, void *cls)
{
	glory_set_callback(GLORY_IDLE, func, cls);
}

static struct window *find_window(int id)
{
	struct window *win = winlist;

	while(win) {
		if(win->id == id) {
			return win;
		}
		win = win->next;
	}
	return 0;
}
